/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

<template>
  <div class="time-charts scroll_hide">
    <div class="rk-trace-t-loading" v-show="loading">
      <svg class="icon loading">
        <use xlink:href="#spinner"></use>
      </svg>
    </div>
    <transition-group name="fade" tag="a" class="mb-5">
      <span class="time-charts-item mr-10" v-for="(i,index) in list" :key="i" :style="`color:${computedScale(index)}`">
         <svg class="icon vm mr-5 sm">
            <use xlink:href="#issue-open-m"></use>
          </svg>
        <span>{{i}}</span>
      </span>
    </transition-group>
    <a class="rk-btn r vm  tc" @click="downloadTrace">{{$t('exportImage')}}</a>
    <rk-sidebox :width="'50%'" :show.sync="showDetail" :title="$t('spanInfo')">
      <div class="rk-trace-detail">
        <h5 class="mb-15">{{$t('tags')}}.</h5>
        <div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('endpoint')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.label}}</span></div>
        <div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('spanType')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.type}}</span></div>
        <div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('component')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.component}}</span></div>
        <div class="mb-10 clear"><span class="g-sm-4 grey">Peer:</span><span class="g-sm-8 wba">{{this.currentSpan.peer||'No Peer'}}</span></div>
        <div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('error')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.isError}}</span></div>
        <div class="mb-10 clear" v-for="i in this.currentSpan.tags" :key="i.key">
          <span class="g-sm-4 grey">{{i.key}}:</span>
          <span class="g-sm-8 wba">
            {{i.value}}
            <svg v-if="i.key==='db.statement'" class="icon vm grey link-hover cp ml-5" @click="copy(i.value)">
              <use xlink:href="#review-list"></use>
            </svg>
          </span>
        </div>
        <h5 class="mb-10" v-if="this.currentSpan.logs" v-show="this.currentSpan.logs.length">{{$t('logs')}}.</h5>
        <div v-for="(i, index) in this.currentSpan.logs" :key="index">
          <div class="mb-10 sm"><span class="mr-10">{{$t('time')}}:</span><span class="grey">{{i.time | dateformat}}</span></div>
          <div class="mb-15 clear" v-for="(_i, _index) in i.data" :key="_index">
            <div class="mb-10">{{_i.key}}:<span v-if="_i.key==='stack'" class="r rk-sidebox-magnify"
                                                @click="showCurrentSpanDetail(_i.key, _i.value)">
          <svg class="icon">
            <use xlink:href="#magnify"></use>
          </svg>
        </span></div><pre class="pl-15 mt-0 mb-0 sm oa" >{{_i.value}}</pre>
          </div>
        </div>
      </div>
    </rk-sidebox>
    <v-dialog width="90%"/>
    <div class="trace-list">
      <div ref="traceList"></div>
    </div>
  </div>
</template>
<script lang="js">
import copy from '@/utils/copy';
import * as d3 from 'd3';
import Trace from './d3-trace';
import _ from 'lodash';
export default {
  props: ['data', 'traceId'],
  data() {
    return {
      segmentId: [],
      showDetail: false,
      list: [],
      currentSpan: [],
      loading: true,
    };
  },
  watch: {
    data() {
      if (!this.data.length) { return; }
      this.loading = true;
      this.changeTree();
      this.tree.init({label: 'TRACE_ROOT', children: this.segmentId}, this.data);
      this.tree.draw(() => {
        setTimeout(() => {
          this.loading = false;
        }, 200);
      });
    },
  },
  beforeDestroy() {
    d3.selectAll('.d3-tip').remove();
  },
  mounted() {
    this.$eventBus.$on('TRACE-LIST-LOADING', this, () => { this.loading = true; });
    // this.loading = true;
    this.changeTree();
    this.tree = new Trace(this.$refs.traceList, this);
    this.tree.init({label: 'TRACE_ROOT', children: this.segmentId}, this.data);
    this.tree.draw();
    this.loading = false;
    // this.computedScale();
  },
  methods: {
    copy,
    handleSelectSpan(i) {
      this.currentSpan = i.data;
      this.showDetail = true;
    },
    traverseTree(node, spanId, segmentId, data) {
      if (!node) { return; }
      if (node.spanId === spanId && node.segmentId === segmentId) {
        node.children.push(data);
        return;
      }
      if (node.children && node.children.length > 0) {
        node.children.forEach((nodeItem) => {
          this.traverseTree(nodeItem, spanId, segmentId, data);
        });
      }
    },
    computedScale(i) {
       // Rainbow map
      const sequentialScale = d3.scaleSequential()
      .domain([0, this.list.length + 1])
      .interpolator(d3.interpolateCool);
      return sequentialScale(i);
    },
    changeTree() {
      if (this.data.length === 0) {
        return [];
      }
      this.list = Array.from(new Set(this.data.map((i) => i.serviceCode)));
      this.segmentId = [];
      const segmentGroup = {};
      const segmentIdGroup = [];
      const fixSpans = [];
      const segmentHeaders = [];
      this.data.forEach((span) => {
        if (span.parentSpanId === -1) {
          segmentHeaders.push(span);
        } else {
          const index = this.data.findIndex((i) => (
            i.segmentId === span.segmentId
            &&
            i.spanId === (span.spanId - 1)
          ));
          const fixSpanKeyContent = {
            traceId: span.traceId,
            segmentId: span.segmentId,
            spanId: span.spanId - 1,
            parentSpanId: span.spanId - 2,
          };
          if (index === -1 && !_.find(fixSpans, fixSpanKeyContent)) {
            fixSpans.push(
              {
                ...fixSpanKeyContent,
                refs: [],
                endpointName: `VNode: ${span.segmentId}`,
                serviceCode: 'VirtualNode',
                type: `[Broken] ${span.type}`,
                peer: '',
                component: `VirtualNode: #${span.spanId - 1}`,
                isError: true,
                isBroken: true,
                layer: 'Broken',
                tags: [],
                logs: [],
              },
            );
          }
        }
      });
      segmentHeaders.forEach((span) => {
        if (span.refs.length) {
          span.refs.forEach((ref) => {
            const index = this.data.findIndex((i) => (
              ref.parentSegmentId === i.segmentId
              &&
              ref.parentSpanId === i.spanId
            ));
            if (index === -1) {
              // create a known broken node.
              const i = ref.parentSpanId;
              const fixSpanKeyContent = {
                traceId: ref.traceId,
                segmentId: ref.parentSegmentId,
                spanId: i,
                parentSpanId: i > -1 ? 0 : -1,
              };
              if (!_.find(fixSpans, fixSpanKeyContent)) {
                fixSpans.push({
                    ...fixSpanKeyContent, refs: [], endpointName: `VNode: ${ref.parentSegmentId}`,
                  serviceCode: 'VirtualNode', type: `[Broken] ${ref.type}`, peer: '', component: `VirtualNode: #${i}`,
                  isError: true, isBroken: true, layer: 'Broken', tags: [], logs: [],
                });
              }
              // if root broken node is not exist, create a root broken node.
              if (fixSpanKeyContent.parentSpanId > -1) {
                const fixRootSpanKeyContent = {
                  traceId: ref.traceId,
                  segmentId: ref.parentSegmentId,
                  spanId: 0,
                  parentSpanId: -1,
                };
                if (!_.find(fixSpans, fixRootSpanKeyContent)) {
                  fixSpans.push({
                    ...fixRootSpanKeyContent,
                    refs: [],
                    endpointName: `VNode: ${ref.parentSegmentId}`,
                    serviceCode: 'VirtualNode',
                    type: `[Broken] ${ref.type}`,
                    peer: '',
                    component: `VirtualNode: #0`,
                    isError: true,
                    isBroken: true,
                    layer: 'Broken',
                    tags: [],
                    logs: [],
                  });
                }
              }
            }
          });
        }
      });
      [...fixSpans, ...this.data].forEach((i) => {
        i.label = i.endpointName || 'no operation name';
        i.children = [];
        if (segmentGroup[i.segmentId] === undefined) {
          segmentIdGroup.push(i.segmentId);
          segmentGroup[i.segmentId] = [];
          segmentGroup[i.segmentId].push(i);
        } else {
          segmentGroup[i.segmentId].push(i);
        }
      });
      segmentIdGroup.forEach((id) => {
        const currentSegment = segmentGroup[id].sort((a, b) => b.parentSpanId - a.parentSpanId);
        currentSegment.forEach((s) => {
          const index = currentSegment.findIndex((i) => i.spanId === s.parentSpanId);
          if (index !== -1) {
            if (
              (currentSegment[index].isBroken && currentSegment[index].parentSpanId === -1)
              ||
              !currentSegment[index].isBroken
            ) {
              currentSegment[index].children.push(s);
              currentSegment[index].children.sort((a, b) => a.spanId - b.spanId);
            }
          }
          if (s.isBroken) {
            const children = _.filter(this.data, (span) => {
              return _.find(span.refs, {traceId: s.traceId, parentSegmentId: s.segmentId, parentSpanId: s.spanId});
            });
            if (children.length > 0) {
              s.children.push(...children);
            }
          }
        });
        segmentGroup[id] = currentSegment[currentSegment.length - 1];
      });
      segmentIdGroup.forEach((id) => {
        segmentGroup[id].refs.forEach((ref) => {
          if (ref.traceId === this.traceId) {
            this.traverseTree(
              segmentGroup[ref.parentSegmentId],
              ref.parentSpanId,
              ref.parentSegmentId,
              segmentGroup[id]);
          }
        });
      });
      for (const i in segmentGroup) {
        if (segmentGroup[i].refs.length === 0 ) {
          this.segmentId.push(segmentGroup[i]);
        }
      }
      this.segmentId.forEach((i) => {
        this.collapse(i);
      });
    },
    collapse(d) {
      if (d.children) {
        let dur = d.endTime - d.startTime;
        d.children.forEach((i) => {
          dur -= (i.endTime - i.startTime);
        });
        d.dur = dur < 0 ? 0 : dur;
        d.children.forEach((i) => this.collapse(i));
      }
    },
    showCurrentSpanDetail(title, text) {
      const textLineNumber = text.split('\n').length;
      let textHeight = textLineNumber * 20.2 + 10;
      const tmpHeight = window.innerHeight * 0.9;
      textHeight = textHeight >= tmpHeight ? tmpHeight : textHeight;
      this.$modal.show('dialog', {
        title,
        text: `<div style="height:${textHeight}px">${text}</div>`,
        buttons: [
          {
            title: 'Copy',
            handler: () => {
              this.copy(text);
            },
          },
          {
            title: 'Close',
          },
        ],
      });
    },
    downloadTrace() {
      const serializer = new XMLSerializer();
      const svgNode = d3.select('.trace-list-dowanload').node();
      const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgNode)}`;
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      canvas.width = d3.select('.trace-list-dowanload')._groups[0][0].clientWidth;
      canvas.height = d3.select('.trace-list-dowanload')._groups[0][0].clientHeight;
      context.fillStyle = '#fff';
      context.fillRect(0, 0, canvas.width, canvas.height);
      const image = new Image();
      image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
      image.onload = () => {
        context.drawImage(image, 0, 0);
        const tagA = document.createElement('a');
        tagA.download = 'trace-list.png';
        tagA.href = canvas.toDataURL('image/png');
        tagA.click();
      };
    },
  },
};
</script>
<style lang="scss">
.time-charts{
  overflow: auto;
  padding: 10px 30px;
  position: relative;
  min-height: 150px;  
}
.trace-node .group {
  cursor: pointer;
  fill-opacity: 0;
}
.trace-node-container{
  fill: rgba(0, 0, 0, 0);
  stroke-width: 5px;
  cursor: pointer;
  &:hover{
    fill: rgba(0,0,0,0.05)
  }
}
.trace-node  .node-text {
  font: 12.5px sans-serif;
  pointer-events: none;
}
.domain{display: none;}
.time-charts-item{
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid;
  font-size: 11px;
  border-radius: 4px;
}
 .trace-list{
   fill: rgba(0,0,0,0)
 }
 .trace-list .trace-node rect{
   &:hover{
     fill: rgba(0,0,0,0.05)
   }
 }
.dialog-c-text {
  white-space: pre;
  overflow: auto;
  font-family: monospace;
}
</style>
